﻿/*
 * Copyright (c) Jason Paul Morley, 2010
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Core
{
    /// <summary>
    /// The global singleton database all edges and nodes are stored within
    /// </summary>
    public class Database
    {
        /// <summary>
        /// Calculates the distance between two points squared
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static double DistanceBewteenPointsSquared(Point a, Point b)
        {
            double x = a.x - b.x;
            double y = a.y - b.y;
            return x*x + y*y;
        }
        
        /// <summary>
        /// Calculates the distance between two points
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static double DistanceBewteenPoints(Point a, Point b)
        {
            return Math.Sqrt(DistanceBewteenPointsSquared(a,b));
        }

        /// <summary>
        /// Returns which point is closest to a: b or c
        /// </summary>
        /// <param name="a">a</param>
        /// <param name="b">b</param>
        /// <param name="c">c</param>
        /// <returns></returns>
        private static Point GetClosest(Point a, Point b, Point c)
        {
            return ( DistanceBewteenPointsSquared(a,b) < DistanceBewteenPointsSquared(a,c) ? b : c );
        }

        /// <summary>
        /// Calculates the minimum distance between a line and a point knowing the point, and the gradient and co-efficient of the line
        /// </summary>
        /// <param name="p">point</param>
        /// <param name="m">gradient</param>
        /// <param name="c">co-efficient</param>
        /// <returns>distance</returns>
        private static double DistanceFromLine(Point p, double m, double c)
        {
            double a = p.x, b = p.y;
            return ((m * a) + (c - b)) / Math.Sqrt(1.0 + m*m);
        }

        /// <summary>
        /// Calculates the minimum distance between a line and a point p, knowing p, the start of and end of the line
        /// </summary>
        /// <param name="p"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        private static double DistanceFromLine(Point p, Point start, Point end)
        {
            if ( start.x > end.x ) return DistanceFromLine(p, end, start); // swap if needed

            if ( start.x == end.x ) // straight vertical
            {
                if ( p.y < start.y ) return DistanceBewteenPoints(p, start);
                if ( p.y > end.y ) return DistanceBewteenPoints(p, end);
                return (p.x - start.x);
            }

            if ( start.y == end.y ) // straight horizontal
            {
                if ( p.x < start.x ) return DistanceBewteenPoints(p, start);
                if ( p.x > end.x ) return DistanceBewteenPoints(p, end);
                return (p.y - start.y);
            }

            if ( p.x < start.x ) return DistanceBewteenPoints(p, start); // if we're past the line we can assume the closest point
            if ( p.x > end.x ) return DistanceBewteenPoints(p, end); // is either the start or end based on which side we lie
            return DistanceFromLine(p, (end.y - start.y) / (end.x - start.x), (double)start.y - ((end.y - start.y) / (end.x - start.x) * start.x));
        }

        /// <summary>
        /// Get all edges connected to a node
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public List<Edge> GetConnectors(Node n)
        {
            List<Edge> r = new List<Edge>();
            foreach (Edge e in _spv_edgeList)
            {
                if (e.node1 == n || e.node2 == n)
                {
                    r.Add(e);
                    Debug.Log("Database", "GetConnectors(" + n.id + ")", "finds (" + e.node1.id + ", " + e.node2.id + ")");
                }
            }
            return r;
        }

        /// <summary>
        /// Get all nodes directly connected to a node by edges
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public List<Node> GetConnected(Node n)
        {
            List<Node> r = new List<Node>();
            List<Edge> edges = GetConnectors(n);
            foreach (Edge e in edges)
            {
                Debug.Log("Database", "GetConnected(" + n.id + ")", "Edge joins (" + e.node1.id + ", " + e.node2.id + ")");
                r.Add(e.node1 == n ? e.node2 : e.node1);
            }
            return r;
        }

        /// <summary>
        /// Get all nodes whose positions lie in a circle
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public List<Node> GetNodesInCircle(Circle c)
        {
            List<Node> r = new List<Node>();
            double radius2 = c.radius * c.radius;

            foreach (Node n in _spv_nodeList)
            {
                Point dV = new Point(n.position.x - c.position.x, n.position.y - c.position.y);
                double dR2 = dV.x * dV.x + dV.y * dV.y;
                if (dR2 <= radius2) r.Add(n);
            }

            return r;
        }

        /// <summary>
        /// Get all edges that pass through the circle
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        public List<Edge> GetEdgesInCircle(Circle c)
        {
            List<Edge> r = new List<Edge>();
            foreach (Edge e in _spv_edgeList)
                if (DistanceFromLine(c.position, e.node1.position, e.node2.position) <= c.radius)
                    r.Add(e);
            return r;
        }

        /// <summary>
        /// Get the closest edge to a point from a list of edges
        /// </summary>
        /// <param name="p"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public Edge GetClosestEdge(Point p, List<Edge> l)
        {
            Edge r = null;
            double d2 = double.PositiveInfinity;
            foreach (Edge e in l)
            {
                double dR2 = DistanceFromLine(p, e.node1.position, e.node2.position);
                if (dR2 < d2)
                {
                    r = e;
                    d2 = dR2;
                }
            }
            return r;
        }

        /// <summary>
        /// Returns the closest node to a point from the passed list of points
        /// </summary>
        /// <param name="l"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public Node GetClosestNode(List<Node> l, Point p)
        {
            Node r = null;
            double d2 = double.PositiveInfinity;
            foreach (Node n in l)
            {
                Point dV = new Point(n.position.x - p.x, n.position.y - p.y);
                double dR2 = dV.x * dV.x + dV.y * dV.y;
                if (dR2 < d2)
                { 
                    r = n;
                    d2 = dR2;
                }
            }
            return r;
        }

        /// <summary>
        /// Gets the edge that joines two nodes, or null if no edge exists
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public Edge GetJoiningEdge(Node a, Node b)
        {
            List<Edge> l = GetConnectors(a);
            foreach (Edge e in l)
            {
                if (   (e.node1 == a && e.node2 == b)
                    || (e.node1 == b && e.node2 == a))
                    return e;
            }
            return null;
        }

        /// <summary>
        /// Get the list of nodes
        /// </summary>
        public List<Node> Nodes { get { return _spv_nodeList; } }

        /// <summary>
        /// Get the list of edges
        /// </summary>
        public List<Edge> Edges { get { return _spv_edgeList; } }

        /// <summary>
        /// Get the list of steps
        /// </summary>
        public List<IStep> Steps { get { return _spv_stepList; } }

        private List<Node> _spv_nodeList = new List<Node>();
        private List<Edge> _spv_edgeList = new List<Edge>();
        private List<IStep> _spv_stepList = new List<IStep>();
    }
}
